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.
27 "golang.org/x/tools/go/ast/astutil"
28 "mvdan.cc/gofumpt/gofumports/internal/gocommand"
31 // Options is golang.org/x/tools/imports.Options with extra internal-only options.
33 Env *ProcessEnv // The environment to use. Note: this contains the cached module and filesystem state.
35 Fragment bool // Accept fragment of a source file (no package statement)
36 AllErrors bool // Report all errors (not just the first 10 on different lines)
38 Comments bool // Print comments (true if nil *Options provided)
39 TabIndent bool // Use tabs for indent (true if nil *Options provided)
40 TabWidth int // Tab width (8 if nil *Options provided)
42 FormatOnly bool // Disable the insertion and deletion of imports
45 // Process implements golang.org/x/tools/imports.Process with explicit context in env.
46 func Process(filename string, src []byte, opt *Options) (formatted []byte, err error) {
47 src, opt, err = initialize(filename, src, opt)
52 fileSet := token.NewFileSet()
53 file, adjust, err := parse(fileSet, filename, src, opt)
59 if err := fixImports(fileSet, file, filename, opt.Env); err != nil {
63 return formatFile(fileSet, file, src, adjust, opt)
66 // FixImports returns a list of fixes to the imports that, when applied,
67 // will leave the imports in the same state as Process.
69 // Note that filename's directory influences which imports can be chosen,
70 // so it is important that filename be accurate.
71 func FixImports(filename string, src []byte, opt *Options) (fixes []*ImportFix, err error) {
72 src, opt, err = initialize(filename, src, opt)
77 fileSet := token.NewFileSet()
78 file, _, err := parse(fileSet, filename, src, opt)
83 return getFixes(fileSet, file, filename, opt.Env)
86 // ApplyFixes applies all of the fixes to the file and formats it. extraMode
87 // is added in when parsing the file.
88 func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) {
89 src, opt, err = initialize(filename, src, opt)
94 // Don't use parse() -- we don't care about fragments or statement lists
95 // here, and we need to work with unparseable files.
96 fileSet := token.NewFileSet()
97 parserMode := parser.Mode(0)
99 parserMode |= parser.ParseComments
102 parserMode |= parser.AllErrors
104 parserMode |= extraMode
106 file, err := parser.ParseFile(fileSet, filename, src, parserMode)
111 // Apply the fixes to the file.
112 apply(fileSet, file, fixes)
114 return formatFile(fileSet, file, src, nil, opt)
117 // GetAllCandidates gets all of the packages starting with prefix that can be
118 // imported by filename, sorted by import path.
119 func GetAllCandidates(ctx context.Context, callback func(ImportFix), searchPrefix, filename, filePkg string, opt *Options) error {
120 _, opt, err := initialize(filename, []byte{}, opt)
124 return getAllCandidates(ctx, callback, searchPrefix, filename, filePkg, opt.Env)
127 // GetPackageExports returns all known packages with name pkg and their exports.
128 func GetPackageExports(ctx context.Context, callback func(PackageExport), searchPkg, filename, filePkg string, opt *Options) error {
129 _, opt, err := initialize(filename, []byte{}, opt)
133 return getPackageExports(ctx, callback, searchPkg, filename, filePkg, opt.Env)
136 // initialize sets the values for opt and src.
137 // If they are provided, they are not changed. Otherwise opt is set to the
138 // default values and src is read from the file system.
139 func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, error) {
140 // Use defaults if opt is nil.
142 opt = &Options{Comments: true, TabIndent: true, TabWidth: 8}
145 // Set the env if the user has not provided it.
147 opt.Env = &ProcessEnv{}
149 // Set the gocmdRunner if the user has not provided it.
150 if opt.Env.GocmdRunner == nil {
151 opt.Env.GocmdRunner = &gocommand.Runner{}
153 if err := opt.Env.init(); err != nil {
158 b, err := ioutil.ReadFile(filename)
168 func formatFile(fileSet *token.FileSet, file *ast.File, src []byte, adjust func(orig []byte, src []byte) []byte, opt *Options) ([]byte, error) {
169 mergeImports(opt.Env, fileSet, file)
170 sortImports(opt.Env, fileSet, file)
171 imps := astutil.Imports(fileSet, file)
172 var spacesBefore []string // import paths we need spaces before
173 for _, impSection := range imps {
174 // Within each block of contiguous imports, see if any
175 // import lines are in different group numbers. If so,
176 // we'll need to put a space between them so it's
177 // compatible with gofmt.
179 for _, importSpec := range impSection {
180 importPath, _ := strconv.Unquote(importSpec.Path.Value)
181 groupNum := importGroup(opt.Env, importPath)
182 if groupNum != lastGroup && lastGroup != -1 {
183 spacesBefore = append(spacesBefore, importPath)
190 printerMode := printer.UseSpaces
192 printerMode |= printer.TabIndent
194 printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth}
197 err := printConfig.Fprint(&buf, fileSet, file)
203 out = adjust(src, out)
205 if len(spacesBefore) > 0 {
206 out, err = addImportSpaces(bytes.NewReader(out), spacesBefore)
212 out, err = format.Source(out)
219 // parse parses src, which was read from filename,
220 // as a Go source file or statement list.
221 func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) {
222 parserMode := parser.Mode(0)
224 parserMode |= parser.ParseComments
227 parserMode |= parser.AllErrors
230 // Try as whole source file.
231 file, err := parser.ParseFile(fset, filename, src, parserMode)
233 return file, nil, nil
235 // If the error is that the source file didn't begin with a
236 // package line and we accept fragmented input, fall through to
237 // try as a source fragment. Stop and return on any other error.
238 if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") {
242 // If this is a declaration list, make it a source file
243 // by inserting a package clause.
244 // Insert using a ;, not a newline, so that parse errors are on
246 const prefix = "package main;"
247 psrc := append([]byte(prefix), src...)
248 file, err = parser.ParseFile(fset, filename, psrc, parserMode)
250 // Gofmt will turn the ; into a \n.
251 // Do that ourselves now and update the file contents,
252 // so that positions and line numbers are correct going forward.
253 psrc[len(prefix)-1] = '\n'
254 fset.File(file.Package).SetLinesForContent(psrc)
256 // If a main function exists, we will assume this is a main
257 // package and leave the file.
258 if containsMainFunc(file) {
259 return file, nil, nil
262 adjust := func(orig, src []byte) []byte {
263 // Remove the package clause.
264 src = src[len(prefix):]
265 return matchSpace(orig, src)
267 return file, adjust, nil
269 // If the error is that the source file didn't begin with a
270 // declaration, fall through to try as a statement list.
271 // Stop and return on any other error.
272 if !strings.Contains(err.Error(), "expected declaration") {
276 // If this is a statement list, make it a source file
277 // by inserting a package clause and turning the list
278 // into a function body. This handles expressions too.
279 // Insert using a ;, not a newline, so that the line numbers
280 // in fsrc match the ones in src.
281 fsrc := append(append([]byte("package p; func _() {"), src...), '}')
282 file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
284 adjust := func(orig, src []byte) []byte {
285 // Remove the wrapping.
286 // Gofmt has turned the ; into a \n\n.
287 src = src[len("package p\n\nfunc _() {"):]
288 src = src[:len(src)-len("}\n")]
289 // Gofmt has also indented the function body one level.
290 // Remove that indent.
291 src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1)
292 return matchSpace(orig, src)
294 return file, adjust, nil
297 // Failed, and out of options.
301 // containsMainFunc checks if a file contains a function declaration with the
302 // function signature 'func main()'
303 func containsMainFunc(file *ast.File) bool {
304 for _, decl := range file.Decls {
305 if f, ok := decl.(*ast.FuncDecl); ok {
306 if f.Name.Name != "main" {
310 if len(f.Type.Params.List) != 0 {
314 if f.Type.Results != nil && len(f.Type.Results.List) != 0 {
325 func cutSpace(b []byte) (before, middle, after []byte) {
327 for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') {
331 for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') {
335 return b[:i], b[i:j], b[j:]
337 return nil, nil, b[j:]
340 // matchSpace reformats src to use the same space context as orig.
341 // 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src.
342 // 2) matchSpace copies the indentation of the first non-blank line in orig
343 // to every non-blank line in src.
344 // 3) matchSpace copies the trailing space from orig and uses it in place
345 // of src's trailing space.
346 func matchSpace(orig []byte, src []byte) []byte {
347 before, _, after := cutSpace(orig)
348 i := bytes.LastIndex(before, []byte{'\n'})
349 before, indent := before[:i+1], before[i+1:]
351 _, src, _ = cutSpace(src)
357 if i := bytes.IndexByte(line, '\n'); i >= 0 {
358 line, src = line[:i+1], line[i+1:]
362 if len(line) > 0 && line[0] != '\n' { // not blank
371 var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+)"`)
373 func addImportSpaces(r io.Reader, breaks []string) ([]byte, error) {
375 in := bufio.NewReader(r)
379 s, err := in.ReadString('\n')
382 } else if err != nil {
386 if !inImports && !done && strings.HasPrefix(s, "import") {
389 if inImports && (strings.HasPrefix(s, "var") ||
390 strings.HasPrefix(s, "func") ||
391 strings.HasPrefix(s, "const") ||
392 strings.HasPrefix(s, "type")) {
396 if inImports && len(breaks) > 0 {
397 if m := impLine.FindStringSubmatch(s); m != nil {
398 if m[1] == breaks[0] {
407 return out.Bytes(), nil