github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/internal/format/format.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package format 6 7 import ( 8 "bytes" 9 "go/ast" 10 "go/parser" 11 "go/printer" 12 "go/token" 13 "strings" 14 ) 15 16 const parserMode = parser.ParseComments 17 18 // Parse parses src, which was read from the named file, 19 // as a Go source file, declaration, or statement list. 20 func Parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( 21 file *ast.File, 22 sourceAdj func(src []byte, indent int) []byte, 23 indentAdj int, 24 err error, 25 ) { 26 // Try as whole source file. 27 file, err = parser.ParseFile(fset, filename, src, parserMode) 28 // If there's no error, return. If the error is that the source file didn't begin with a 29 // package line and source fragments are ok, fall through to 30 // try as a source fragment. Stop and return on any other error. 31 if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { 32 return 33 } 34 35 // If this is a declaration list, make it a source file 36 // by inserting a package clause. 37 // Insert using a ;, not a newline, so that the line numbers 38 // in psrc match the ones in src. 39 psrc := append([]byte("package p;"), src...) 40 file, err = parser.ParseFile(fset, filename, psrc, parserMode) 41 if err == nil { 42 sourceAdj = func(src []byte, indent int) []byte { 43 // Remove the package clause. 44 // Gofmt has turned the ; into a \n. 45 src = src[indent+len("package p\n"):] 46 return bytes.TrimSpace(src) 47 } 48 return 49 } 50 // If the error is that the source file didn't begin with a 51 // declaration, fall through to try as a statement list. 52 // Stop and return on any other error. 53 if !strings.Contains(err.Error(), "expected declaration") { 54 return 55 } 56 57 // If this is a statement list, make it a source file 58 // by inserting a package clause and turning the list 59 // into a function body. This handles expressions too. 60 // Insert using a ;, not a newline, so that the line numbers 61 // in fsrc match the ones in src. 62 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}') 63 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) 64 if err == nil { 65 sourceAdj = func(src []byte, indent int) []byte { 66 // Cap adjusted indent to zero. 67 if indent < 0 { 68 indent = 0 69 } 70 // Remove the wrapping. 71 // Gofmt has turned the ; into a \n\n. 72 // There will be two non-blank lines with indent, hence 2*indent. 73 src = src[2*indent+len("package p\n\nfunc _() {"):] 74 src = src[:len(src)-(indent+len("\n}\n"))] 75 return bytes.TrimSpace(src) 76 } 77 // Gofmt has also indented the function body one level. 78 // Adjust that with indentAdj. 79 indentAdj = -1 80 } 81 82 // Succeeded, or out of options. 83 return 84 } 85 86 // Format formats the given package file originally obtained from src 87 // and adjusts the result based on the original source via sourceAdj 88 // and indentAdj. 89 func Format( 90 fset *token.FileSet, 91 file *ast.File, 92 sourceAdj func(src []byte, indent int) []byte, 93 indentAdj int, 94 src []byte, 95 cfg printer.Config, 96 ) ([]byte, error) { 97 if sourceAdj == nil { 98 // Complete source file. 99 var buf bytes.Buffer 100 err := cfg.Fprint(&buf, fset, file) 101 if err != nil { 102 return nil, err 103 } 104 return buf.Bytes(), nil 105 } 106 107 // Partial source file. 108 // Determine and prepend leading space. 109 i, j := 0, 0 110 for j < len(src) && IsSpace(src[j]) { 111 if src[j] == '\n' { 112 i = j + 1 // byte offset of last line in leading space 113 } 114 j++ 115 } 116 var res []byte 117 res = append(res, src[:i]...) 118 119 // Determine and prepend indentation of first code line. 120 // Spaces are ignored unless there are no tabs, 121 // in which case spaces count as one tab. 122 indent := 0 123 hasSpace := false 124 for _, b := range src[i:j] { 125 switch b { 126 case ' ': 127 hasSpace = true 128 case '\t': 129 indent++ 130 } 131 } 132 if indent == 0 && hasSpace { 133 indent = 1 134 } 135 for i := 0; i < indent; i++ { 136 res = append(res, '\t') 137 } 138 139 // Format the source. 140 // Write it without any leading and trailing space. 141 cfg.Indent = indent + indentAdj 142 var buf bytes.Buffer 143 err := cfg.Fprint(&buf, fset, file) 144 if err != nil { 145 return nil, err 146 } 147 res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...) 148 149 // Determine and append trailing space. 150 i = len(src) 151 for i > 0 && IsSpace(src[i-1]) { 152 i-- 153 } 154 return append(res, src[i:]...), nil 155 } 156 157 // IsSpace reports whether the byte is a space character. 158 // IsSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. 159 func IsSpace(b byte) bool { 160 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 161 }