github.com/hongwozai/go-src-1.4.3@v0.0.0-20191127132709-dc3fce3dbccb/src/go/format/format.go (about)

     1  // Copyright 2012 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 format implements standard formatting of Go source.
     6  package format
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/parser"
    13  	"go/printer"
    14  	"go/token"
    15  	"io"
    16  	"strings"
    17  )
    18  
    19  var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
    20  
    21  const parserMode = parser.ParseComments
    22  
    23  // Node formats node in canonical gofmt style and writes the result to dst.
    24  //
    25  // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl,
    26  // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec,
    27  // or ast.Stmt. Node does not modify node. Imports are not sorted for
    28  // nodes representing partial source files (i.e., if the node is not an
    29  // *ast.File or a *printer.CommentedNode not wrapping an *ast.File).
    30  //
    31  // The function may return early (before the entire result is written)
    32  // and return a formatting error, for instance due to an incorrect AST.
    33  //
    34  func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
    35  	// Determine if we have a complete source file (file != nil).
    36  	var file *ast.File
    37  	var cnode *printer.CommentedNode
    38  	switch n := node.(type) {
    39  	case *ast.File:
    40  		file = n
    41  	case *printer.CommentedNode:
    42  		if f, ok := n.Node.(*ast.File); ok {
    43  			file = f
    44  			cnode = n
    45  		}
    46  	}
    47  
    48  	// Sort imports if necessary.
    49  	if file != nil && hasUnsortedImports(file) {
    50  		// Make a copy of the AST because ast.SortImports is destructive.
    51  		// TODO(gri) Do this more efficiently.
    52  		var buf bytes.Buffer
    53  		err := config.Fprint(&buf, fset, file)
    54  		if err != nil {
    55  			return err
    56  		}
    57  		file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
    58  		if err != nil {
    59  			// We should never get here. If we do, provide good diagnostic.
    60  			return fmt.Errorf("format.Node internal error (%s)", err)
    61  		}
    62  		ast.SortImports(fset, file)
    63  
    64  		// Use new file with sorted imports.
    65  		node = file
    66  		if cnode != nil {
    67  			node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
    68  		}
    69  	}
    70  
    71  	return config.Fprint(dst, fset, node)
    72  }
    73  
    74  // Source formats src in canonical gofmt style and returns the result
    75  // or an (I/O or syntax) error. src is expected to be a syntactically
    76  // correct Go source file, or a list of Go declarations or statements.
    77  //
    78  // If src is a partial source file, the leading and trailing space of src
    79  // is applied to the result (such that it has the same leading and trailing
    80  // space as src), and the result is indented by the same amount as the first
    81  // line of src containing code. Imports are not sorted for partial source files.
    82  //
    83  func Source(src []byte) ([]byte, error) {
    84  	fset := token.NewFileSet()
    85  	file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	if sourceAdj == nil {
    91  		// Complete source file.
    92  		// TODO(gri) consider doing this always.
    93  		ast.SortImports(fset, file)
    94  	}
    95  
    96  	return format(fset, file, sourceAdj, indentAdj, src, config)
    97  }
    98  
    99  func hasUnsortedImports(file *ast.File) bool {
   100  	for _, d := range file.Decls {
   101  		d, ok := d.(*ast.GenDecl)
   102  		if !ok || d.Tok != token.IMPORT {
   103  			// Not an import declaration, so we're done.
   104  			// Imports are always first.
   105  			return false
   106  		}
   107  		if d.Lparen.IsValid() {
   108  			// For now assume all grouped imports are unsorted.
   109  			// TODO(gri) Should check if they are sorted already.
   110  			return true
   111  		}
   112  		// Ungrouped imports are sorted by default.
   113  	}
   114  	return false
   115  }
   116  
   117  // ----------------------------------------------------------------------------
   118  // Support functions
   119  //
   120  // The functions parse, format, and isSpace below are identical to the
   121  // respective functions in cmd/gofmt/gofmt.go - keep them in sync!
   122  //
   123  // TODO(gri) Factor out this functionality, eventually.
   124  
   125  // parse parses src, which was read from the named file,
   126  // as a Go source file, declaration, or statement list.
   127  func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
   128  	file *ast.File,
   129  	sourceAdj func(src []byte, indent int) []byte,
   130  	indentAdj int,
   131  	err error,
   132  ) {
   133  	// Try as whole source file.
   134  	file, err = parser.ParseFile(fset, filename, src, parserMode)
   135  	// If there's no error, return.  If the error is that the source file didn't begin with a
   136  	// package line and source fragments are ok, fall through to
   137  	// try as a source fragment.  Stop and return on any other error.
   138  	if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
   139  		return
   140  	}
   141  
   142  	// If this is a declaration list, make it a source file
   143  	// by inserting a package clause.
   144  	// Insert using a ;, not a newline, so that the line numbers
   145  	// in psrc match the ones in src.
   146  	psrc := append([]byte("package p;"), src...)
   147  	file, err = parser.ParseFile(fset, filename, psrc, parserMode)
   148  	if err == nil {
   149  		sourceAdj = func(src []byte, indent int) []byte {
   150  			// Remove the package clause.
   151  			// Gofmt has turned the ; into a \n.
   152  			src = src[indent+len("package p\n"):]
   153  			return bytes.TrimSpace(src)
   154  		}
   155  		return
   156  	}
   157  	// If the error is that the source file didn't begin with a
   158  	// declaration, fall through to try as a statement list.
   159  	// Stop and return on any other error.
   160  	if !strings.Contains(err.Error(), "expected declaration") {
   161  		return
   162  	}
   163  
   164  	// If this is a statement list, make it a source file
   165  	// by inserting a package clause and turning the list
   166  	// into a function body.  This handles expressions too.
   167  	// Insert using a ;, not a newline, so that the line numbers
   168  	// in fsrc match the ones in src.
   169  	fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}')
   170  	file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
   171  	if err == nil {
   172  		sourceAdj = func(src []byte, indent int) []byte {
   173  			// Cap adjusted indent to zero.
   174  			if indent < 0 {
   175  				indent = 0
   176  			}
   177  			// Remove the wrapping.
   178  			// Gofmt has turned the ; into a \n\n.
   179  			// There will be two non-blank lines with indent, hence 2*indent.
   180  			src = src[2*indent+len("package p\n\nfunc _() {"):]
   181  			src = src[:len(src)-(indent+len("\n}\n"))]
   182  			return bytes.TrimSpace(src)
   183  		}
   184  		// Gofmt has also indented the function body one level.
   185  		// Adjust that with indentAdj.
   186  		indentAdj = -1
   187  	}
   188  
   189  	// Succeeded, or out of options.
   190  	return
   191  }
   192  
   193  // format formats the given package file originally obtained from src
   194  // and adjusts the result based on the original source via sourceAdj
   195  // and indentAdj.
   196  func format(
   197  	fset *token.FileSet,
   198  	file *ast.File,
   199  	sourceAdj func(src []byte, indent int) []byte,
   200  	indentAdj int,
   201  	src []byte,
   202  	cfg printer.Config,
   203  ) ([]byte, error) {
   204  	if sourceAdj == nil {
   205  		// Complete source file.
   206  		var buf bytes.Buffer
   207  		err := cfg.Fprint(&buf, fset, file)
   208  		if err != nil {
   209  			return nil, err
   210  		}
   211  		return buf.Bytes(), nil
   212  	}
   213  
   214  	// Partial source file.
   215  	// Determine and prepend leading space.
   216  	i, j := 0, 0
   217  	for j < len(src) && isSpace(src[j]) {
   218  		if src[j] == '\n' {
   219  			i = j + 1 // byte offset of last line in leading space
   220  		}
   221  		j++
   222  	}
   223  	var res []byte
   224  	res = append(res, src[:i]...)
   225  
   226  	// Determine and prepend indentation of first code line.
   227  	// Spaces are ignored unless there are no tabs,
   228  	// in which case spaces count as one tab.
   229  	indent := 0
   230  	hasSpace := false
   231  	for _, b := range src[i:j] {
   232  		switch b {
   233  		case ' ':
   234  			hasSpace = true
   235  		case '\t':
   236  			indent++
   237  		}
   238  	}
   239  	if indent == 0 && hasSpace {
   240  		indent = 1
   241  	}
   242  	for i := 0; i < indent; i++ {
   243  		res = append(res, '\t')
   244  	}
   245  
   246  	// Format the source.
   247  	// Write it without any leading and trailing space.
   248  	cfg.Indent = indent + indentAdj
   249  	var buf bytes.Buffer
   250  	err := cfg.Fprint(&buf, fset, file)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...)
   255  
   256  	// Determine and append trailing space.
   257  	i = len(src)
   258  	for i > 0 && isSpace(src[i-1]) {
   259  		i--
   260  	}
   261  	return append(res, src[i:]...), nil
   262  }
   263  
   264  func isSpace(b byte) bool {
   265  	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
   266  }