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

Delta Between Two Patch Sets: src/go/format/format.go

Issue 142360043: code review 142360043: go/format, cmd/gofmt: fix issues with partial Go code w... (Closed)
Left Patch Set: diff -r 932fe22207465e6c4bcdae29f5c519ba069f8927 https://code.google.com/p/go Created 9 years, 6 months ago
Right Patch Set: diff -r 8f36a32a9d036f86fb956c8538142eb384bd1a4a 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:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « src/cmd/gofmt/testdata/stdin7.input ('k') | src/go/format/format_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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
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 }
LEFTRIGHT

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