github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/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  // Node formats node in canonical gofmt style and writes the result to dst.
    22  //
    23  // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl,
    24  // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec,
    25  // or ast.Stmt. Node does not modify node. Imports are not sorted for
    26  // nodes representing partial source files (i.e., if the node is not an
    27  // *ast.File or a *printer.CommentedNode not wrapping an *ast.File).
    28  //
    29  // The function may return early (before the entire result is written)
    30  // and return a formatting error, for instance due to an incorrect AST.
    31  //
    32  func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
    33  	// Determine if we have a complete source file (file != nil).
    34  	var file *ast.File
    35  	var cnode *printer.CommentedNode
    36  	switch n := node.(type) {
    37  	case *ast.File:
    38  		file = n
    39  	case *printer.CommentedNode:
    40  		if f, ok := n.Node.(*ast.File); ok {
    41  			file = f
    42  			cnode = n
    43  		}
    44  	}
    45  
    46  	// Sort imports if necessary.
    47  	if file != nil && hasUnsortedImports(file) {
    48  		// Make a copy of the AST because ast.SortImports is destructive.
    49  		// TODO(gri) Do this more efficiently.
    50  		var buf bytes.Buffer
    51  		err := config.Fprint(&buf, fset, file)
    52  		if err != nil {
    53  			return err
    54  		}
    55  		file, err = parser.ParseFile(fset, "", buf.Bytes(), parser.ParseComments)
    56  		if err != nil {
    57  			// We should never get here. If we do, provide good diagnostic.
    58  			return fmt.Errorf("format.Node internal error (%s)", err)
    59  		}
    60  		ast.SortImports(fset, file)
    61  
    62  		// Use new file with sorted imports.
    63  		node = file
    64  		if cnode != nil {
    65  			node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
    66  		}
    67  	}
    68  
    69  	return config.Fprint(dst, fset, node)
    70  }
    71  
    72  // Source formats src in canonical gofmt style and returns the result
    73  // or an (I/O or syntax) error. src is expected to be a syntactically
    74  // correct Go source file, or a list of Go declarations or statements.
    75  //
    76  // If src is a partial source file, the leading and trailing space of src
    77  // is applied to the result (such that it has the same leading and trailing
    78  // space as src), and the result is indented by the same amount as the first
    79  // line of src containing code. Imports are not sorted for partial source files.
    80  //
    81  func Source(src []byte) ([]byte, error) {
    82  	fset := token.NewFileSet()
    83  	node, err := parse(fset, src)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	var buf bytes.Buffer
    89  	if file, ok := node.(*ast.File); ok {
    90  		// Complete source file.
    91  		ast.SortImports(fset, file)
    92  		err := config.Fprint(&buf, fset, file)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  
    97  	} else {
    98  		// Partial source file.
    99  		// Determine and prepend leading space.
   100  		i, j := 0, 0
   101  		for j < len(src) && isSpace(src[j]) {
   102  			if src[j] == '\n' {
   103  				i = j + 1 // index of last line in leading space
   104  			}
   105  			j++
   106  		}
   107  		buf.Write(src[:i])
   108  
   109  		// Determine indentation of first code line.
   110  		// Spaces are ignored unless there are no tabs,
   111  		// in which case spaces count as one tab.
   112  		indent := 0
   113  		hasSpace := false
   114  		for _, b := range src[i:j] {
   115  			switch b {
   116  			case ' ':
   117  				hasSpace = true
   118  			case '\t':
   119  				indent++
   120  			}
   121  		}
   122  		if indent == 0 && hasSpace {
   123  			indent = 1
   124  		}
   125  
   126  		// Format the source.
   127  		cfg := config
   128  		cfg.Indent = indent
   129  		err := cfg.Fprint(&buf, fset, node)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  
   134  		// Determine and append trailing space.
   135  		i = len(src)
   136  		for i > 0 && isSpace(src[i-1]) {
   137  			i--
   138  		}
   139  		buf.Write(src[i:])
   140  	}
   141  
   142  	return buf.Bytes(), nil
   143  }
   144  
   145  func hasUnsortedImports(file *ast.File) bool {
   146  	for _, d := range file.Decls {
   147  		d, ok := d.(*ast.GenDecl)
   148  		if !ok || d.Tok != token.IMPORT {
   149  			// Not an import declaration, so we're done.
   150  			// Imports are always first.
   151  			return false
   152  		}
   153  		if d.Lparen.IsValid() {
   154  			// For now assume all grouped imports are unsorted.
   155  			// TODO(gri) Should check if they are sorted already.
   156  			return true
   157  		}
   158  		// Ungrouped imports are sorted by default.
   159  	}
   160  	return false
   161  }
   162  
   163  func isSpace(b byte) bool {
   164  	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
   165  }
   166  
   167  func parse(fset *token.FileSet, src []byte) (interface{}, error) {
   168  	// Try as a complete source file.
   169  	file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
   170  	if err == nil {
   171  		return file, nil
   172  	}
   173  	// If the source is missing a package clause, try as a source fragment; otherwise fail.
   174  	if !strings.Contains(err.Error(), "expected 'package'") {
   175  		return nil, err
   176  	}
   177  
   178  	// Try as a declaration list by prepending a package clause in front of src.
   179  	// Use ';' not '\n' to keep line numbers intact.
   180  	psrc := append([]byte("package p;"), src...)
   181  	file, err = parser.ParseFile(fset, "", psrc, parser.ParseComments)
   182  	if err == nil {
   183  		return file.Decls, nil
   184  	}
   185  	// If the source is missing a declaration, try as a statement list; otherwise fail.
   186  	if !strings.Contains(err.Error(), "expected declaration") {
   187  		return nil, err
   188  	}
   189  
   190  	// Try as statement list by wrapping a function around src.
   191  	fsrc := append(append([]byte("package p; func _() {"), src...), '}')
   192  	file, err = parser.ParseFile(fset, "", fsrc, parser.ParseComments)
   193  	if err == nil {
   194  		return file.Decls[0].(*ast.FuncDecl).Body.List, nil
   195  	}
   196  
   197  	// Failed, and out of options.
   198  	return nil, err
   199  }