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  }