Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(875)

Side by Side Diff: src/go/format/format.go

Issue 142360043: code review 142360043: go/format, cmd/gofmt: fix issues with partial Go code w... (Closed)
Patch Set: diff -r 032fdf145bf989b31b6c7d369c36c4b54066f55a https://code.google.com/p/go Created 9 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
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
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 }
OLDNEW

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b