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