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