Left: | ||
Right: |
OLD | NEW |
---|---|
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 config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidt h: 8} | 19 var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidt h: 8} |
20 | 20 |
21 const parserMode = parser.ParseComments | |
22 | |
21 // 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. |
22 // | 24 // |
23 // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, | 25 // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, |
24 // []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, |
25 // 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 |
26 // 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 |
27 // *ast.File or a *printer.CommentedNode not wrapping an *ast.File). | 29 // *ast.File or a *printer.CommentedNode not wrapping an *ast.File). |
28 // | 30 // |
29 // The function may return early (before the entire result is written) | 31 // The function may return early (before the entire result is written) |
30 // and return a formatting error, for instance due to an incorrect AST. | 32 // and return a formatting error, for instance due to an incorrect AST. |
(...skipping 14 matching lines...) Expand all Loading... | |
45 | 47 |
46 // Sort imports if necessary. | 48 // Sort imports if necessary. |
47 if file != nil && hasUnsortedImports(file) { | 49 if file != nil && hasUnsortedImports(file) { |
48 // Make a copy of the AST because ast.SortImports is destructive . | 50 // Make a copy of the AST because ast.SortImports is destructive . |
49 // TODO(gri) Do this more efficiently. | 51 // TODO(gri) Do this more efficiently. |
50 var buf bytes.Buffer | 52 var buf bytes.Buffer |
51 err := config.Fprint(&buf, fset, file) | 53 err := config.Fprint(&buf, fset, file) |
52 if err != nil { | 54 if err != nil { |
53 return err | 55 return err |
54 } | 56 } |
55 » » file, err = parser.ParseFile(fset, "", buf.Bytes(), parser.Parse Comments) | 57 » » file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode) |
56 if err != nil { | 58 if err != nil { |
57 // We should never get here. If we do, provide good diag nostic. | 59 // We should never get here. If we do, provide good diag nostic. |
58 return fmt.Errorf("format.Node internal error (%s)", err ) | 60 return fmt.Errorf("format.Node internal error (%s)", err ) |
59 } | 61 } |
60 ast.SortImports(fset, file) | 62 ast.SortImports(fset, file) |
61 | 63 |
62 // Use new file with sorted imports. | 64 // Use new file with sorted imports. |
63 node = file | 65 node = file |
64 if cnode != nil { | 66 if cnode != nil { |
65 node = &printer.CommentedNode{Node: file, Comments: cnod e.Comments} | 67 node = &printer.CommentedNode{Node: file, Comments: cnod e.Comments} |
66 } | 68 } |
67 } | 69 } |
68 | 70 |
69 return config.Fprint(dst, fset, node) | 71 return config.Fprint(dst, fset, node) |
70 } | 72 } |
71 | 73 |
72 // Source formats src in canonical gofmt style and returns the result | 74 // 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 | 75 // 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. | 76 // correct Go source file, or a list of Go declarations or statements. |
75 // | 77 // |
76 // 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 |
77 // 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 |
78 // 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 |
79 // 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. |
80 // | 82 // |
81 func Source(src []byte) ([]byte, error) { | 83 func Source(src []byte) ([]byte, error) { |
82 fset := token.NewFileSet() | 84 fset := token.NewFileSet() |
83 » node, err := parse(fset, src) | 85 » file, adjust, adjustIndent, err := parse(fset, "", src, true) |
gri
2014/09/25 17:36:05
file, sourceAdj, indentAdj, err :=
| |
84 if err != nil { | 86 if err != nil { |
85 return nil, err | 87 return nil, err |
86 } | 88 } |
87 | 89 |
88 » var buf bytes.Buffer | 90 » var res []byte |
gri
2014/09/25 17:36:05
remove this
shurcooL
2014/09/26 06:03:35
It's possible to do this here in format.go because
| |
89 » if file, ok := node.(*ast.File); ok { | 91 » if adjust == nil { |
90 // Complete source file. | 92 // Complete source file. |
91 ast.SortImports(fset, file) | 93 ast.SortImports(fset, file) |
94 var buf bytes.Buffer | |
92 err := config.Fprint(&buf, fset, file) | 95 err := config.Fprint(&buf, fset, file) |
93 if err != nil { | 96 if err != nil { |
94 return nil, err | 97 return nil, err |
95 } | 98 } |
99 res = buf.Bytes() | |
gri
2014/09/25 17:36:06
return buf.Bytes(), nil
| |
96 | 100 |
97 } else { | 101 } else { |
gri
2014/09/25 17:36:05
no need for else anymore - outdent entire block be
| |
98 // Partial source file. | 102 // Partial source file. |
99 // Determine and prepend leading space. | 103 // Determine and prepend leading space. |
100 i, j := 0, 0 | 104 i, j := 0, 0 |
101 for j < len(src) && isSpace(src[j]) { | 105 for j < len(src) && isSpace(src[j]) { |
102 if src[j] == '\n' { | 106 if src[j] == '\n' { |
103 » » » » i = j + 1 // index of last line in leading space | 107 » » » » i = j + 1 // byte offset of last line in leading space |
104 } | 108 } |
105 j++ | 109 j++ |
106 } | 110 } |
107 » » buf.Write(src[:i]) | 111 » » res = append(res, src[:i]...) |
108 | 112 |
109 » » // Determine indentation of first code line. | 113 » » // Determine and prepend indentation of first code line. |
110 // Spaces are ignored unless there are no tabs, | 114 // Spaces are ignored unless there are no tabs, |
111 // in which case spaces count as one tab. | 115 // in which case spaces count as one tab. |
112 indent := 0 | 116 indent := 0 |
113 hasSpace := false | 117 hasSpace := false |
114 for _, b := range src[i:j] { | 118 for _, b := range src[i:j] { |
115 switch b { | 119 switch b { |
116 case ' ': | 120 case ' ': |
117 hasSpace = true | 121 hasSpace = true |
118 case '\t': | 122 case '\t': |
119 indent++ | 123 indent++ |
120 } | 124 } |
121 } | 125 } |
122 if indent == 0 && hasSpace { | 126 if indent == 0 && hasSpace { |
123 indent = 1 | 127 indent = 1 |
124 } | 128 } |
129 for i := 0; i < indent; i++ { | |
130 res = append(res, '\t') | |
131 } | |
125 | 132 |
126 // Format the source. | 133 // Format the source. |
134 // Write it without any leading and trailing space. | |
127 cfg := config | 135 cfg := config |
128 » » cfg.Indent = indent | 136 » » cfg.Indent = indent + adjustIndent |
gri
2014/09/25 17:36:05
s/adjustIndent/indentAdj/
| |
129 » » err := cfg.Fprint(&buf, fset, node) | 137 » » var buf bytes.Buffer |
138 » » err := cfg.Fprint(&buf, fset, file) | |
130 if err != nil { | 139 if err != nil { |
131 return nil, err | 140 return nil, err |
132 } | 141 } |
142 res = append(res, adjust(buf.Bytes(), indent+adjustIndent)...) | |
gri
2014/09/25 17:36:05
s/indent + adjustIndent/cfg.Indent/
| |
133 | 143 |
134 // Determine and append trailing space. | 144 // Determine and append trailing space. |
135 i = len(src) | 145 i = len(src) |
136 for i > 0 && isSpace(src[i-1]) { | 146 for i > 0 && isSpace(src[i-1]) { |
137 i-- | 147 i-- |
138 } | 148 } |
139 » » buf.Write(src[i:]) | 149 » » res = append(res, src[i:]...) |
gri
2014/09/25 17:36:05
return append(res, src[i:]...), nil
| |
140 } | 150 } |
141 | 151 |
142 » return buf.Bytes(), nil | 152 » return res, nil |
gri
2014/09/25 17:36:05
remove
| |
143 } | 153 } |
144 | 154 |
145 func hasUnsortedImports(file *ast.File) bool { | 155 func hasUnsortedImports(file *ast.File) bool { |
146 for _, d := range file.Decls { | 156 for _, d := range file.Decls { |
147 d, ok := d.(*ast.GenDecl) | 157 d, ok := d.(*ast.GenDecl) |
148 if !ok || d.Tok != token.IMPORT { | 158 if !ok || d.Tok != token.IMPORT { |
149 // Not an import declaration, so we're done. | 159 // Not an import declaration, so we're done. |
150 // Imports are always first. | 160 // Imports are always first. |
151 return false | 161 return false |
152 } | 162 } |
153 if d.Lparen.IsValid() { | 163 if d.Lparen.IsValid() { |
154 // For now assume all grouped imports are unsorted. | 164 // For now assume all grouped imports are unsorted. |
155 // TODO(gri) Should check if they are sorted already. | 165 // TODO(gri) Should check if they are sorted already. |
156 return true | 166 return true |
157 } | 167 } |
158 // Ungrouped imports are sorted by default. | 168 // Ungrouped imports are sorted by default. |
159 } | 169 } |
160 return false | 170 return false |
161 } | 171 } |
162 | 172 |
173 // parse parses src, which was read from filename, | |
174 // as a Go source file or statement list. | |
175 func parse(fset *token.FileSet, filename string, src []byte, stdin bool) (*ast.F ile, func(src []byte, indent int) []byte, int, error) { | |
gri
2014/09/25 17:36:05
There are too many result parameters now to leave
| |
176 // Try as whole source file. | |
177 file, err := parser.ParseFile(fset, filename, src, parserMode) | |
gri
2014/09/25 17:36:05
s/:=/=/
| |
178 if err == nil { | |
gri
2014/09/25 17:36:05
I think you can combine this one with the next if
| |
179 return file, nil, 0, nil | |
180 } | |
181 // If the error is that the source file didn't begin with a | |
gri
2014/09/25 17:36:05
If there's no error, or the error is that ...
shurcooL
2014/09/26 06:54:22
This comment change doesn't sound right.
// If th
| |
182 // package line and this is standard input, fall through to | |
gri
2014/09/25 17:36:05
...and source fragments are ok, fall through...
| |
183 // try as a source fragment. Stop and return on any other error. | |
184 if !stdin || !strings.Contains(err.Error(), "expected 'package'") { | |
gri
2014/09/25 17:36:05
if err == nil || !fragmentOk || ... {
| |
185 return nil, nil, 0, err | |
gri
2014/09/25 17:36:06
just
return
| |
186 } | |
187 | |
188 // If this is a declaration list, make it a source file | |
189 // by inserting a package clause. | |
190 // Insert using a ;, not a newline, so that the line numbers | |
191 // in psrc match the ones in src. | |
192 psrc := append([]byte("package p;"), src...) | |
193 file, err = parser.ParseFile(fset, filename, psrc, parserMode) | |
194 if err == nil { | |
195 adjust := func(src []byte, indent int) []byte { | |
gri
2014/09/25 17:36:05
sourceAdj =
| |
196 // Remove the package clause. | |
197 // Gofmt has turned the ; into a \n. | |
198 src = src[indent+len("package p\n"):] | |
199 return bytes.TrimSpace(src) | |
200 } | |
201 adjustIndent := 0 | |
gri
2014/09/25 17:36:05
no need to set
| |
202 return file, adjust, adjustIndent, nil | |
gri
2014/09/25 17:36:05
just
return
| |
203 } | |
204 // If the error is that the source file didn't begin with a | |
205 // declaration, fall through to try as a statement list. | |
206 // Stop and return on any other error. | |
207 if !strings.Contains(err.Error(), "expected declaration") { | |
208 return nil, nil, 0, err | |
gri
2014/09/25 17:36:05
just
return
| |
209 } | |
210 | |
211 // If this is a statement list, make it a source file | |
212 // by inserting a package clause and turning the list | |
213 // into a function body. This handles expressions too. | |
214 // Insert using a ;, not a newline, so that the line numbers | |
215 // in fsrc match the ones in src. | |
216 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '} ') | |
217 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) | |
218 if err == nil { | |
219 adjust := func(src []byte, indent int) []byte { | |
gri
2014/09/25 17:36:05
sourceAdj =
| |
220 // Cap adjusted indent to zero. | |
221 if indent < 0 { | |
222 indent = 0 | |
223 } | |
224 // Remove the wrapping. | |
225 // Gofmt has turned the ; into a \n\n. | |
226 // There will be two non-blank lines with indent, hence 2*indent. | |
227 src = src[2*indent+len("package p\n\nfunc _() {"):] | |
228 src = src[:len(src)-(indent+len("\n}\n"))] | |
229 return bytes.TrimSpace(src) | |
230 } | |
231 // Gofmt has also indented the function body one level. | |
232 // Remove that indent by returning adjustIndent value of -1. | |
gri
2014/09/25 17:36:05
// Adjust that with indentAdj.
(no need to repeat
| |
233 adjustIndent := -1 | |
gri
2014/09/25 17:36:05
indentAdj = -1
| |
234 return file, adjust, adjustIndent, nil | |
gri
2014/09/25 17:36:05
remove, just fall through
| |
235 } | |
236 | |
237 // Failed, and out of options. | |
gri
2014/09/25 17:36:05
change to
// Succeeded, or out of options.
| |
238 return nil, nil, 0, err | |
gri
2014/09/25 17:36:05
just
return
| |
239 } | |
240 | |
163 func isSpace(b byte) bool { | 241 func isSpace(b byte) bool { |
164 return b == ' ' || b == '\t' || b == '\n' || b == '\r' | 242 return b == ' ' || b == '\t' || b == '\n' || b == '\r' |
165 } | 243 } |
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; othe rwise 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 } | |
OLD | NEW |