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  }