github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/imports/imports.go (about) 1 // Copyright 2013 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 imports implements a Go pretty-printer (like package "go/format") 6 // that also adds or removes import statements as necessary. 7 package imports // import "golang.org/x/tools/imports" 8 9 import ( 10 "bufio" 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/format" 15 "go/parser" 16 "go/printer" 17 "go/token" 18 "io" 19 "regexp" 20 "strconv" 21 "strings" 22 23 "golang.org/x/tools/go/ast/astutil" 24 ) 25 26 // Options specifies options for processing files. 27 type Options struct { 28 Fragment bool // Accept fragment of a source file (no package statement) 29 AllErrors bool // Report all errors (not just the first 10 on different lines) 30 31 Comments bool // Print comments (true if nil *Options provided) 32 TabIndent bool // Use tabs for indent (true if nil *Options provided) 33 TabWidth int // Tab width (8 if nil *Options provided) 34 } 35 36 // Process formats and adjusts imports for the provided file. 37 // If opt is nil the defaults are used. 38 func Process(filename string, src []byte, opt *Options) ([]byte, error) { 39 if opt == nil { 40 opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} 41 } 42 43 fileSet := token.NewFileSet() 44 file, adjust, err := parse(fileSet, filename, src, opt) 45 if err != nil { 46 return nil, err 47 } 48 49 _, err = fixImports(fileSet, file) 50 if err != nil { 51 return nil, err 52 } 53 54 sortImports(fileSet, file) 55 imps := astutil.Imports(fileSet, file) 56 57 var spacesBefore []string // import paths we need spaces before 58 for _, impSection := range imps { 59 // Within each block of contiguous imports, see if any 60 // import lines are in different group numbers. If so, 61 // we'll need to put a space between them so it's 62 // compatible with gofmt. 63 lastGroup := -1 64 for _, importSpec := range impSection { 65 importPath, _ := strconv.Unquote(importSpec.Path.Value) 66 groupNum := importGroup(importPath) 67 if groupNum != lastGroup && lastGroup != -1 { 68 spacesBefore = append(spacesBefore, importPath) 69 } 70 lastGroup = groupNum 71 } 72 73 } 74 75 printerMode := printer.UseSpaces 76 if opt.TabIndent { 77 printerMode |= printer.TabIndent 78 } 79 printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} 80 81 var buf bytes.Buffer 82 err = printConfig.Fprint(&buf, fileSet, file) 83 if err != nil { 84 return nil, err 85 } 86 out := buf.Bytes() 87 if adjust != nil { 88 out = adjust(src, out) 89 } 90 if len(spacesBefore) > 0 { 91 out = addImportSpaces(bytes.NewReader(out), spacesBefore) 92 } 93 94 out, err = format.Source(out) 95 if err != nil { 96 return nil, err 97 } 98 return out, nil 99 } 100 101 // parse parses src, which was read from filename, 102 // as a Go source file or statement list. 103 func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) { 104 parserMode := parser.Mode(0) 105 if opt.Comments { 106 parserMode |= parser.ParseComments 107 } 108 if opt.AllErrors { 109 parserMode |= parser.AllErrors 110 } 111 112 // Try as whole source file. 113 file, err := parser.ParseFile(fset, filename, src, parserMode) 114 if err == nil { 115 return file, nil, nil 116 } 117 // If the error is that the source file didn't begin with a 118 // package line and we accept fragmented input, fall through to 119 // try as a source fragment. Stop and return on any other error. 120 if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") { 121 return nil, nil, err 122 } 123 124 // If this is a declaration list, make it a source file 125 // by inserting a package clause. 126 // Insert using a ;, not a newline, so that the line numbers 127 // in psrc match the ones in src. 128 psrc := append([]byte("package main;"), src...) 129 file, err = parser.ParseFile(fset, filename, psrc, parserMode) 130 if err == nil { 131 // If a main function exists, we will assume this is a main 132 // package and leave the file. 133 if containsMainFunc(file) { 134 return file, nil, nil 135 } 136 137 adjust := func(orig, src []byte) []byte { 138 // Remove the package clause. 139 // Gofmt has turned the ; into a \n. 140 src = src[len("package main\n"):] 141 return matchSpace(orig, src) 142 } 143 return file, adjust, nil 144 } 145 // If the error is that the source file didn't begin with a 146 // declaration, fall through to try as a statement list. 147 // Stop and return on any other error. 148 if !strings.Contains(err.Error(), "expected declaration") { 149 return nil, nil, err 150 } 151 152 // If this is a statement list, make it a source file 153 // by inserting a package clause and turning the list 154 // into a function body. This handles expressions too. 155 // Insert using a ;, not a newline, so that the line numbers 156 // in fsrc match the ones in src. 157 fsrc := append(append([]byte("package p; func _() {"), src...), '}') 158 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) 159 if err == nil { 160 adjust := func(orig, src []byte) []byte { 161 // Remove the wrapping. 162 // Gofmt has turned the ; into a \n\n. 163 src = src[len("package p\n\nfunc _() {"):] 164 src = src[:len(src)-len("}\n")] 165 // Gofmt has also indented the function body one level. 166 // Remove that indent. 167 src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1) 168 return matchSpace(orig, src) 169 } 170 return file, adjust, nil 171 } 172 173 // Failed, and out of options. 174 return nil, nil, err 175 } 176 177 // containsMainFunc checks if a file contains a function declaration with the 178 // function signature 'func main()' 179 func containsMainFunc(file *ast.File) bool { 180 for _, decl := range file.Decls { 181 if f, ok := decl.(*ast.FuncDecl); ok { 182 if f.Name.Name != "main" { 183 continue 184 } 185 186 if len(f.Type.Params.List) != 0 { 187 continue 188 } 189 190 if f.Type.Results != nil && len(f.Type.Results.List) != 0 { 191 continue 192 } 193 194 return true 195 } 196 } 197 198 return false 199 } 200 201 func cutSpace(b []byte) (before, middle, after []byte) { 202 i := 0 203 for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') { 204 i++ 205 } 206 j := len(b) 207 for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') { 208 j-- 209 } 210 if i <= j { 211 return b[:i], b[i:j], b[j:] 212 } 213 return nil, nil, b[j:] 214 } 215 216 // matchSpace reformats src to use the same space context as orig. 217 // 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src. 218 // 2) matchSpace copies the indentation of the first non-blank line in orig 219 // to every non-blank line in src. 220 // 3) matchSpace copies the trailing space from orig and uses it in place 221 // of src's trailing space. 222 func matchSpace(orig []byte, src []byte) []byte { 223 before, _, after := cutSpace(orig) 224 i := bytes.LastIndex(before, []byte{'\n'}) 225 before, indent := before[:i+1], before[i+1:] 226 227 _, src, _ = cutSpace(src) 228 229 var b bytes.Buffer 230 b.Write(before) 231 for len(src) > 0 { 232 line := src 233 if i := bytes.IndexByte(line, '\n'); i >= 0 { 234 line, src = line[:i+1], line[i+1:] 235 } else { 236 src = nil 237 } 238 if len(line) > 0 && line[0] != '\n' { // not blank 239 b.Write(indent) 240 } 241 b.Write(line) 242 } 243 b.Write(after) 244 return b.Bytes() 245 } 246 247 var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+)"`) 248 249 func addImportSpaces(r io.Reader, breaks []string) []byte { 250 var out bytes.Buffer 251 sc := bufio.NewScanner(r) 252 inImports := false 253 done := false 254 for sc.Scan() { 255 s := sc.Text() 256 257 if !inImports && !done && strings.HasPrefix(s, "import") { 258 inImports = true 259 } 260 if inImports && (strings.HasPrefix(s, "var") || 261 strings.HasPrefix(s, "func") || 262 strings.HasPrefix(s, "const") || 263 strings.HasPrefix(s, "type")) { 264 done = true 265 inImports = false 266 } 267 if inImports && len(breaks) > 0 { 268 if m := impLine.FindStringSubmatch(s); m != nil { 269 if m[1] == string(breaks[0]) { 270 out.WriteByte('\n') 271 breaks = breaks[1:] 272 } 273 } 274 } 275 276 fmt.Fprintln(&out, s) 277 } 278 return out.Bytes() 279 }