github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/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  )
    17  
    18  var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
    19  
    20  const parserMode = parser.ParseComments
    21  
    22  // Node formats node in canonical gofmt style and writes the result to dst.
    23  //
    24  // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl,
    25  // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec,
    26  // or ast.Stmt. Node does not modify node. Imports are not sorted for
    27  // nodes representing partial source files (i.e., if the node is not an
    28  // *ast.File or a *printer.CommentedNode not wrapping an *ast.File).
    29  //
    30  // The function may return early (before the entire result is written)
    31  // and return a formatting error, for instance due to an incorrect AST.
    32  //
    33  func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
    34  	// Determine if we have a complete source file (file != nil).
    35  	var file *ast.File
    36  	var cnode *printer.CommentedNode
    37  	switch n := node.(type) {
    38  	case *ast.File:
    39  		file = n
    40  	case *printer.CommentedNode:
    41  		if f, ok := n.Node.(*ast.File); ok {
    42  			file = f
    43  			cnode = n
    44  		}
    45  	}
    46  
    47  	// Sort imports if necessary.
    48  	if file != nil && hasUnsortedImports(file) {
    49  		// Make a copy of the AST because ast.SortImports is destructive.
    50  		// TODO(gri) Do this more efficiently.
    51  		var buf bytes.Buffer
    52  		err := config.Fprint(&buf, fset, file)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
    57  		if err != nil {
    58  			// We should never get here. If we do, provide good diagnostic.
    59  			return fmt.Errorf("format.Node internal error (%s)", err)
    60  		}
    61  		ast.SortImports(fset, file)
    62  
    63  		// Use new file with sorted imports.
    64  		node = file
    65  		if cnode != nil {
    66  			node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
    67  		}
    68  	}
    69  
    70  	return config.Fprint(dst, fset, node)
    71  }
    72  
    73  // Source formats src in canonical gofmt style and returns the result
    74  // or an (I/O or syntax) error. src is expected to be a syntactically
    75  // correct Go source file, or a list of Go declarations or statements.
    76  //
    77  // If src is a partial source file, the leading and trailing space of src
    78  // is applied to the result (such that it has the same leading and trailing
    79  // space as src), and the result is indented by the same amount as the first
    80  // line of src containing code. Imports are not sorted for partial source files.
    81  //
    82  func Source(src []byte) ([]byte, error) {
    83  	fset := token.NewFileSet()
    84  	file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	if sourceAdj == nil {
    90  		// Complete source file.
    91  		// TODO(gri) consider doing this always.
    92  		ast.SortImports(fset, file)
    93  	}
    94  
    95  	return format(fset, file, sourceAdj, indentAdj, src, config)
    96  }
    97  
    98  func hasUnsortedImports(file *ast.File) bool {
    99  	for _, d := range file.Decls {
   100  		d, ok := d.(*ast.GenDecl)
   101  		if !ok || d.Tok != token.IMPORT {
   102  			// Not an import declaration, so we're done.
   103  			// Imports are always first.
   104  			return false
   105  		}
   106  		if d.Lparen.IsValid() {
   107  			// For now assume all grouped imports are unsorted.
   108  			// TODO(gri) Should check if they are sorted already.
   109  			return true
   110  		}
   111  		// Ungrouped imports are sorted by default.
   112  	}
   113  	return false
   114  }