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.
5 //go:generate go run mkstdlib.go
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"
27 "golang.org/x/tools/go/ast/astutil"
30 // Options specifies options for processing files.
32 Fragment bool // Accept fragment of a source file (no package statement)
33 AllErrors bool // Report all errors (not just the first 10 on different lines)
35 Comments bool // Print comments (true if nil *Options provided)
36 TabIndent bool // Use tabs for indent (true if nil *Options provided)
37 TabWidth int // Tab width (8 if nil *Options provided)
39 FormatOnly bool // Disable the insertion and deletion of imports
42 // Process formats and adjusts imports for the provided file.
43 // If opt is nil the defaults are used.
45 // Note that filename's directory influences which imports can be chosen,
46 // so it is important that filename be accurate.
47 // To process data ``as if'' it were in filename, pass the data as a non-nil src.
48 func Process(filename string, src []byte, opt *Options) ([]byte, error) {
49 env := &fixEnv{GOPATH: build.Default.GOPATH, GOROOT: build.Default.GOROOT}
50 return process(filename, src, opt, env)
53 func process(filename string, src []byte, opt *Options, env *fixEnv) ([]byte, error) {
55 opt = &Options{Comments: true, TabIndent: true, TabWidth: 8}
58 b, err := ioutil.ReadFile(filename)
65 fileSet := token.NewFileSet()
66 file, adjust, err := parse(fileSet, filename, src, opt)
72 if err := fixImports(fileSet, file, filename, env); err != nil {
77 sortImports(fileSet, file)
78 imps := astutil.Imports(fileSet, file)
79 var spacesBefore []string // import paths we need spaces before
80 for _, impSection := range imps {
81 // Within each block of contiguous imports, see if any
82 // import lines are in different group numbers. If so,
83 // we'll need to put a space between them so it's
84 // compatible with gofmt.
86 for _, importSpec := range impSection {
87 importPath, _ := strconv.Unquote(importSpec.Path.Value)
88 groupNum := importGroup(importPath)
89 if groupNum != lastGroup && lastGroup != -1 {
90 spacesBefore = append(spacesBefore, importPath)
97 printerMode := printer.UseSpaces
99 printerMode |= printer.TabIndent
101 printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth}
104 err = printConfig.Fprint(&buf, fileSet, file)
110 out = adjust(src, out)
112 if len(spacesBefore) > 0 {
113 out, err = addImportSpaces(bytes.NewReader(out), spacesBefore)
119 out, err = format.Source(out)
126 // parse parses src, which was read from filename,
127 // as a Go source file or statement list.
128 func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) {
129 parserMode := parser.Mode(0)
131 parserMode |= parser.ParseComments
134 parserMode |= parser.AllErrors
137 // Try as whole source file.
138 file, err := parser.ParseFile(fset, filename, src, parserMode)
140 return file, nil, nil
142 // If the error is that the source file didn't begin with a
143 // package line and we accept fragmented input, fall through to
144 // try as a source fragment. Stop and return on any other error.
145 if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") {
149 // If this is a declaration list, make it a source file
150 // by inserting a package clause.
151 // Insert using a ;, not a newline, so that parse errors are on
153 const prefix = "package main;"
154 psrc := append([]byte(prefix), src...)
155 file, err = parser.ParseFile(fset, filename, psrc, parserMode)
157 // Gofmt will turn the ; into a \n.
158 // Do that ourselves now and update the file contents,
159 // so that positions and line numbers are correct going forward.
160 psrc[len(prefix)-1] = '\n'
161 fset.File(file.Package).SetLinesForContent(psrc)
163 // If a main function exists, we will assume this is a main
164 // package and leave the file.
165 if containsMainFunc(file) {
166 return file, nil, nil
169 adjust := func(orig, src []byte) []byte {
170 // Remove the package clause.
171 src = src[len(prefix):]
172 return matchSpace(orig, src)
174 return file, adjust, nil
176 // If the error is that the source file didn't begin with a
177 // declaration, fall through to try as a statement list.
178 // Stop and return on any other error.
179 if !strings.Contains(err.Error(), "expected declaration") {
183 // If this is a statement list, make it a source file
184 // by inserting a package clause and turning the list
185 // into a function body. This handles expressions too.
186 // Insert using a ;, not a newline, so that the line numbers
187 // in fsrc match the ones in src.
188 fsrc := append(append([]byte("package p; func _() {"), src...), '}')
189 file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
191 adjust := func(orig, src []byte) []byte {
192 // Remove the wrapping.
193 // Gofmt has turned the ; into a \n\n.
194 src = src[len("package p\n\nfunc _() {"):]
195 src = src[:len(src)-len("}\n")]
196 // Gofmt has also indented the function body one level.
197 // Remove that indent.
198 src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1)
199 return matchSpace(orig, src)
201 return file, adjust, nil
204 // Failed, and out of options.
208 // containsMainFunc checks if a file contains a function declaration with the
209 // function signature 'func main()'
210 func containsMainFunc(file *ast.File) bool {
211 for _, decl := range file.Decls {
212 if f, ok := decl.(*ast.FuncDecl); ok {
213 if f.Name.Name != "main" {
217 if len(f.Type.Params.List) != 0 {
221 if f.Type.Results != nil && len(f.Type.Results.List) != 0 {
232 func cutSpace(b []byte) (before, middle, after []byte) {
234 for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') {
238 for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') {
242 return b[:i], b[i:j], b[j:]
244 return nil, nil, b[j:]
247 // matchSpace reformats src to use the same space context as orig.
248 // 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src.
249 // 2) matchSpace copies the indentation of the first non-blank line in orig
250 // to every non-blank line in src.
251 // 3) matchSpace copies the trailing space from orig and uses it in place
252 // of src's trailing space.
253 func matchSpace(orig []byte, src []byte) []byte {
254 before, _, after := cutSpace(orig)
255 i := bytes.LastIndex(before, []byte{'\n'})
256 before, indent := before[:i+1], before[i+1:]
258 _, src, _ = cutSpace(src)
264 if i := bytes.IndexByte(line, '\n'); i >= 0 {
265 line, src = line[:i+1], line[i+1:]
269 if len(line) > 0 && line[0] != '\n' { // not blank
278 var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+)"`)
280 func addImportSpaces(r io.Reader, breaks []string) ([]byte, error) {
282 in := bufio.NewReader(r)
286 s, err := in.ReadString('\n')
289 } else if err != nil {
293 if !inImports && !done && strings.HasPrefix(s, "import") {
296 if inImports && (strings.HasPrefix(s, "var") ||
297 strings.HasPrefix(s, "func") ||
298 strings.HasPrefix(s, "const") ||
299 strings.HasPrefix(s, "type")) {
303 if inImports && len(breaks) > 0 {
304 if m := impLine.FindStringSubmatch(s); m != nil {
305 if m[1] == breaks[0] {
314 return out.Bytes(), nil